/*
 * Copyright 2010 Chad Retz
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package org.jdocng.parse.qdox;

import org.jdocng.shared.context.Context;
import org.jdocng.shared.model.SourcePositionImpl;
import org.jdocng.shared.model.TagImpl;
import org.jdocng.util.JavadocUtils;

import com.thoughtworks.qdox.JavaDocBuilder;
import com.thoughtworks.qdox.model.AbstractBaseJavaEntity;
import com.thoughtworks.qdox.model.AbstractJavaEntity;
import com.thoughtworks.qdox.model.DocletTag;
import com.thoughtworks.qdox.model.JavaClass;
import com.thoughtworks.qdox.model.JavaField;
import com.thoughtworks.qdox.model.JavaMethod;
import com.thoughtworks.qdox.model.JavaPackage;
import com.thoughtworks.qdox.model.JavaParameter;
import com.thoughtworks.qdox.model.TypeVariable;

/**
 * Utilities for QDox stuff
 *
 * @author Chad Retz
 */
public class QDoxUtils {

    /**
     * Gets a flat signature for a method
     * 
     * @param method
     * @return
     */
    public static String getFlatSignature(JavaMethod method) {
        StringBuilder builder = new StringBuilder("(");
        for (JavaParameter parameter : method.getParameters()) {
            if (builder.length() > 1) {
                builder.append(", ");
            }
            builder.append(parameter.getType().getValue());
            builder.append(' ');
            builder.append(parameter.getName());
        }
        builder.append(')');
        return builder.toString();
    }
    
    public static boolean isInstanceOf(JavaClass clazz, String qualifiedSuper) {
        if (qualifiedSuper.equals(clazz.getFullyQualifiedName())) {
            return true;
        }
        JavaClass parent = clazz.getSuperJavaClass();
        return parent != null && isInstanceOf(parent, qualifiedSuper);
    }
    
    public static TagImpl[] getFirstSentenceTags(Context context,
            AbstractJavaEntity entity) {
        TagImpl newTag = new TagImpl();
        newTag.setKind("Text");
        //TODO column!
        newTag.setPosition(new SourcePositionImpl(entity.getLineNumber(), 
                -1, entity.getSource().getURL().toExternalForm()));
        newTag.setText(JavadocUtils.getFirstSentence(entity.getComment(), 
                context.getConfiguration().getLocale()));
        //TODO inline tags!
        return new TagImpl[] { newTag };
    }
    
    public static TagImpl[] getFirstSentenceTags(Context context,
            DocletTag docTag) {
        TagImpl newTag = new TagImpl();
        newTag.setKind("Text");
        //TODO column!
        newTag.setPosition(new SourcePositionImpl(docTag.getLineNumber(), 
                -1, ((AbstractJavaEntity) docTag.getContext()).
                getSource().getURL().toExternalForm()));
        newTag.setText(JavadocUtils.getFirstSentence(
                docTag.getValue(), context.getConfiguration().getLocale()));
        //TODO inline tags!
        return new TagImpl[] { newTag };
    }
    
    /**
     * Whether or not the passed in method or class contains
     * the given type parameter
     *  
     * @param entity
     * @param name
     * @return
     */
    public static boolean containsTypeParameter(AbstractBaseJavaEntity entity,
            String name) {
        TypeVariable[] vars;
        if (entity instanceof JavaMethod) {
            vars = ((JavaMethod) entity).getTypeParameters();
        } else if (entity instanceof JavaClass) {
            vars = ((JavaClass) entity).getTypeParameters();
        } else {
            return false;
        }
        for (TypeVariable var : vars) {
            if (name.equals(var.getName())) {
                return true;
            }
        }
        return false;
    }

    /**
     * Whether or not the method contains the given parameter
     * 
     * @param entity
     * @param name
     * @return
     */
    public static boolean containsParameter(AbstractBaseJavaEntity entity,
            String name) {
        if (entity instanceof JavaMethod) {
            for (JavaParameter parameter : ((JavaMethod) entity).getParameters()) {
                if (name.equals(parameter.getName())) {
                    return true;
                }
            }
        }
        return false;
    }
    
    /**
     * Get the friendly name of the given entity
     * 
     * @param entity
     * @return
     */
    public static String getFriendlyName(AbstractBaseJavaEntity entity) {
        if (entity instanceof JavaMethod) {
            return getFriendlyName(((JavaMethod) entity).getParentClass()) +
                    "#" + entity.getName();
        } else {
            return entity.getName();
        }
    }
    
    /**
     * Log a message
     * 
     * @param context
     * @param entity
     * @param line
     * @param message
     * @param parameters
     */
    public static void log(Context context, AbstractBaseJavaEntity entity, 
            int line, String message, String... parameters) {
        String file = null;
        if (entity instanceof AbstractJavaEntity) {
            file = ((AbstractJavaEntity) entity).getSource().getURL().toExternalForm();
        }
        SourcePositionImpl pos = new SourcePositionImpl(line, 
                0, file);
        String log = getFriendlyName(entity) + " - " +
            context.getMessages().getMessage(message, parameters);
        if (message.startsWith("error")) {
            context.getLogger().printError(pos, log);
        } else if (message.startsWith("warn")) {
            context.getLogger().printWarning(pos, log);
        } else {
            context.getLogger().printNotice(pos, log);
        }
    }
    
    /**
     * Get the referencing package if this is a package
     * 
     * @param seeWord
     * @param builder
     * @return
     */
    public static JavaPackage getReferencingPackage(String seeWord,
            JavaDocBuilder builder) {
        if (JavadocUtils.isValidJavaName(seeWord, true)) {
            return builder.getPackageByName(seeWord);
        }
        return null;
    }
    
    /**
     * Get a referencing member from the given entity
     * 
     * @param seeWord
     * @param builder
     * @param entity
     * @return
     */
    public static AbstractJavaEntity getReferencingMember(String seeWord,
            JavaDocBuilder builder, AbstractJavaEntity entity) {
        if (seeWord == null || seeWord.isEmpty()) {
            return null;
        }
        String className = seeWord;
        int index = className.indexOf('#');
        String member = null;
        String signature = null;
        if (index != -1) {
            member = className.substring(index + 1);
            className = className.substring(0, index);
            index = member.indexOf('(');
            if (index != -1) {
                signature = member.substring(index + 1,
                        member.lastIndexOf(')'));
                member = member.substring(0, index);
            }
        }
        //gotta be valid java name on left side
        if ((!className.isEmpty() && !JavadocUtils.
                isValidJavaName(className, true)) ||
                (member != null && !JavadocUtils.isValidJavaName(
                        member, false))) {
            return null;
        }
        //no class? must be local
        if ((className == null || className.isEmpty()) &&
                member != null) {
            return getReferencingMember(entity.getParentClass(), 
                    member, signature);
        }
        //now, let's see if we can first find the class qualified
        JavaClass clazz = builder.getClassByName(className);
        if (clazz != null) {
            AbstractJavaEntity ret = getReferencingMember(clazz, 
                    member, signature);
            if (ret != null) {
                return ret;
            }
        }
        //current package
        AbstractJavaEntity ret = getReferencingMember(entity.getSource().getPackage(), 
                builder, className, member, signature);
        if (ret != null) {
            return ret;
        }
        //now we go through all the imports :-(
        for (String impt : entity.getSource().getImports()) {
            if (impt.endsWith(className)) {
                clazz = builder.getClassByName(impt);
                if (member == null) {
                    return clazz;
                }
                //try this class
                ret = getReferencingMember(
                        clazz, member, signature);
                if (ret != null) {
                    return ret;
                }
            } else if (impt.endsWith(".*")) {
                //all classes in the package
                JavaPackage pkg = builder.getPackageByName(impt.
                        substring(0, impt.length() - 2));
                if (pkg != null) {
                    ret = getReferencingMember(
                            pkg, builder, className, member, signature);
                    if (ret != null) {
                        return ret;
                    }
                }
            }
        }
        return null;
    }
    
    /**
     * Get a referencing member from the given package
     * 
     * @param pkg
     * @param builder
     * @param className
     * @param member
     * @param signature
     * @return
     */
    public static AbstractJavaEntity getReferencingMember(JavaPackage pkg,
            JavaDocBuilder builder, String className, 
            String member, String signature) {
        for (JavaClass pkgClass : pkg.getClasses()) {
            if (className.equals(pkgClass.getName())) {
                if (member == null) {
                    return pkgClass;
                }
                AbstractJavaEntity ret = getReferencingMember(
                        builder.getClassByName(className), member, signature);
                if (ret != null) {
                    return ret;
                }
            }
        }
        return null;
    }
    
    /**
     * Get the referencing member in this class
     * 
     * @param clazz
     * @param member
     * @param signature
     * @return
     */
    public static AbstractJavaEntity getReferencingMember(JavaClass clazz,
            String member, String signature) {
        if (signature != null) {
            //must be method
            boolean constructor = member.equals(clazz.getName());
            for (JavaMethod method : clazz.getMethods(true)) {
                if (((constructor && method.isConstructor()) ||
                        member.equals(method.getName())) &&
                        signature.equals(getFlatSignature(method))) {
                    return method;
                }
            }
            return null;
        } else {
            //field?
            JavaField field = clazz.getFieldByName(member);
            if (field != null) {
                return field;
            }
            //try parents
            JavaClass parent = clazz.getSuperJavaClass();
            if (parent != null) {
                return getReferencingMember(parent, member, signature);
            }
            return null;
        }
    }
    
    private QDoxUtils() {
    }
    
}
